﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Windows.Media;

namespace Kokomo.PeInspector.Reflection
{
	/// <summary>
	/// Describes view information for a member.
	/// </summary>
	/// <remarks>
	/// The string returned by <see cref="Heading"/> does not reference
	/// the type declaring the member.  If the scope of the member can be
	/// easily deduced by the user from the context, then use <see cref="Heading"/>.
	/// Otherwise, use <see cref="LongHeading"/>, which includes the declaring scope
	/// (type or namespace).
	/// Use <see cref="Title"/> as the title for an element when the document
	/// provides the user with scope information.
	/// </remarks>
	public struct MemberViewInfo
	{
		/// <summary>
		/// Gets the member to view.
		/// </summary>
		public MemberInfo Member { get; private set; }

		/// <summary>
		/// Initializes a new instance of <see cref="MemberViewInfo"/>.
		/// </summary>
		/// <param name="heading">The member heading.</param>
		/// <param name="title">The title of the member.</param>
		/// <param name="longHeading">The member long heading.</param>
		/// <param name="iconSource">The member icon.</param>
		public MemberViewInfo(
			UiString heading,
			UiString title,
			UiString longHeading,
			string iconKey
			)
			: this()
		{
			if (heading == null) throw new ArgumentNullException("heading");
			if (longHeading == null) throw new ArgumentNullException("longHeading");

			//this.IconSource = iconSource;
			this.IconKey = iconKey;
			this.Heading = heading;
			this.Title = title;
			this.LongHeading = longHeading;
		}

		/// <summary>
		/// Gets the member heading.
		/// </summary>
		/// <remarks>
		/// See remarks on <see cref="MemberViewInfo"/> for usage notes.
		/// </remarks>
		public UiString Heading { get; private set; }
		/// <summary>
		/// Gets the member title.
		/// </summary>
		/// <remarks>
		/// See remarks on <see cref="MemberViewInfo"/> for usage notes.
		/// </remarks>
		public UiString Title { get; private set; }
		/// <summary>
		/// Gets the member long heading.
		/// </summary>
		/// <remarks>
		/// See remarks on <see cref="MemberViewInfo"/> for usage notes.
		/// </remarks>
		public UiString LongHeading { get; private set; }
		/// <summary>
		/// Gets the icon key representing the member.
		/// </summary>
		public string IconKey { get; private set; }
		//public ImageSource IconSource { get; private set; }

		#region View info
		private static string GetParameterText(MethodBase method, ParameterInfo parameter)
		{
			if (parameter == null) throw new ArgumentNullException("parameter");

			string text =
				GetTypeReferenceText(parameter.ParameterType, method)
				+ " "
				+ parameter.Name
				;

			return text;
		}
		public static MemberViewInfo GetMemberViewInfo(object obj)
		{
			MemberInfo member = obj as MemberInfo;
			if (member != null)
			{
				return GetMemberViewInfo(member);
			}
			else
			{
				Assembly assembly = obj as Assembly;
				if (assembly != null)
				{
					return GetMemberViewInfo(assembly);
				}
				else
				{
					return new MemberViewInfo();
				}
			}
		}
		public static MemberViewInfo GetMemberViewInfo(Assembly assembly)
		{
			string heading = assembly.GetName().Name;
			string longHeading = assembly.FullName;
			MemberViewInfo viewInfo = new MemberViewInfo(
				UiString.FromString(heading),
				UiString.FromString(heading),
				UiString.FromString(longHeading),
				MetadataIconKeys.Assembly
				);

			return viewInfo;
		}
		public static string GetMemberName(MemberInfo member)
		{
			if (member == null) throw new ArgumentNullException("member");

			string memberName = member.PrincipalName();
			if (memberName == null)
				memberName = string.Format("<unnamed{0}_{1:X}>", member.MemberType, member.MetadataToken);

			return memberName;
		}
		public static MemberViewInfo GetMemberViewInfo(MemberInfo member)
		{
			if (member == null) throw new ArgumentNullException("member");

			string memberName = GetMemberName(member);

			string heading = null;
			string longHeading = null;
			string iconKey = null;
			string title = null;

			switch (member.MemberType)
			{
				case MemberTypes.Constructor:
				case MemberTypes.Method:
					{
						MethodBase methodBase = (MethodBase)member;

						if (methodBase.IsPrivate)
							iconKey = MetadataIconKeys.PrivateMethod;
						else if (methodBase.IsFamily)
							iconKey = MetadataIconKeys.FamilyMethod;
						else if (methodBase.IsAssembly)
							iconKey = MetadataIconKeys.AssemblyMethod;
						else
							iconKey = MetadataIconKeys.Method;

						heading = BuildMethodBaseHeading(methodBase);
						string scope =
							(member.ReflectedType != null)
							? member.ReflectedType.QualifiedFullName()
							: member.Module.Name
							;
						longHeading = string.Format("[{0}] {1}", scope, heading);

						if (member.MemberType == MemberTypes.Constructor)
						{
							title = member.ReflectedType.LocalName() + " Constructor";
						}
						else
						{
							title = heading + " Method";
						}
					}
					break;
				case MemberTypes.Event:
					{
						EventInfo eventInfo = (EventInfo)member;

						MethodInfo accessor = eventInfo.GetAddMethod(true);
						if (accessor != null)
						{
							if (accessor.IsPrivate)
								iconKey = MetadataIconKeys.PrivateEvent;
							else if (accessor.IsFamily)
								iconKey = MetadataIconKeys.FamilyEvent;
							else if (accessor.IsAssembly)
								iconKey = MetadataIconKeys.AssemblyEvent;
							else
								iconKey = MetadataIconKeys.Event;
						}
						else
						{
							iconKey = MetadataIconKeys.Event;
						}

						heading = string.Format(
							"{0} : {1}",
							eventInfo.Name,
							GetTypeReferenceText(eventInfo.EventHandlerType, eventInfo)
							);
						longHeading = string.Format("[{0}] {1}", member.ReflectedType.QualifiedFullName(), heading);
						title = eventInfo.Name + " Event";
					}
					break;
				case MemberTypes.Field:
					{
						FieldInfo field = (FieldInfo)member;

						if (field.IsPrivate)
							iconKey = MetadataIconKeys.PrivateField;
						else if (field.IsFamily)
							iconKey = MetadataIconKeys.FamilyField;
						else if (field.IsAssembly)
							iconKey = MetadataIconKeys.AssemblyField;
						else
							iconKey = MetadataIconKeys.Field;

						heading = string.Format(
							"{0} : {1}",
							field.Name,
							GetTypeReferenceText(field.FieldType, field)
							);
						longHeading = string.Format("[{0}] {1}", member.ReflectedType.QualifiedFullName(), heading);
						title = field.Name + " Field";
					}
					break;
				case MemberTypes.NestedType:
				case MemberTypes.TypeInfo:
					{
						Type type = (Type)member;
						heading = type.LocalName();
						longHeading = type.QualifiedFullName();
						if (type.IsClass)
						{
							if (type.IsSubclassOf(typeof(Delegate)))
							{
								if (type.IsNestedPrivate)
									iconKey = MetadataIconKeys.PrivateDelegate;
								else if (type.IsNestedFamily)
									iconKey = MetadataIconKeys.FamilyDelegate;
								else if (type.IsNestedAssembly || !type.IsVisible)
									iconKey = MetadataIconKeys.AssemblyDelegate;
								else
									iconKey = MetadataIconKeys.Delegate;
							}
							else
							{
								if (type.IsNestedPrivate)
									iconKey = MetadataIconKeys.PrivateClass;
								else if (type.IsNestedFamily)
									iconKey = MetadataIconKeys.FamilyClass;
								else if (type.IsNestedAssembly || !type.IsVisible)
									iconKey = MetadataIconKeys.AssemblyClass;
								else
									iconKey = MetadataIconKeys.Class;
							}
						}
						else if (type.IsInterface)
						{
							if (type.IsNestedPrivate)
								iconKey = MetadataIconKeys.PrivateInterface;
							else if (type.IsNestedFamily)
								iconKey = MetadataIconKeys.FamilyInterface;
							else if (type.IsNestedAssembly || !type.IsVisible)
								iconKey = MetadataIconKeys.AssemblyInterface;
							else
								iconKey = MetadataIconKeys.Interface;
						}
						else if (type.IsEnum)
						{
							if (type.IsNestedPrivate)
								iconKey = MetadataIconKeys.PrivateEnum;
							else if (type.IsNestedFamily)
								iconKey = MetadataIconKeys.FamilyEnum;
							else if (type.IsNestedAssembly || !type.IsVisible)
								iconKey = MetadataIconKeys.AssemblyEnum;
							else
								iconKey = MetadataIconKeys.Enum;
						}
						else if (type.IsValueType)
						{
							if (type.IsNestedPrivate)
								iconKey = MetadataIconKeys.PrivateStruct;
							else if (type.IsNestedFamily)
								iconKey = MetadataIconKeys.FamilyStruct;
							else if (type.IsNestedAssembly || !type.IsVisible)
								iconKey = MetadataIconKeys.AssemblyStruct;
							else
								iconKey = MetadataIconKeys.Struct;
						}

						title = heading + " " + type.Kind().ToString();
					}
					break;
				case MemberTypes.Property:
					{
						PropertyInfo property = (PropertyInfo)member;
						heading = string.Format(
							"{0} : {1}",
							property.Name,
							GetTypeReferenceText(property.PropertyType, property)
							);
						MethodInfo accessor = property.GetAccessors(true).FirstOrDefault();
						if (accessor != null)
						{
							if (accessor.IsPrivate)
								iconKey = MetadataIconKeys.PrivateProperty;
							else if (accessor.IsFamily)
								iconKey = MetadataIconKeys.FamilyProperty;
							else if (accessor.IsAssembly)
								iconKey = MetadataIconKeys.AssemblyProperty;
							else
								iconKey = MetadataIconKeys.Property;
						}
						else
						{
							iconKey = MetadataIconKeys.Property;
						}
						longHeading = string.Format("[{0}] {1}", member.ReflectedType.FullName, heading);
						title = property.Name + " Property";
					}
					break;
				default:
					break;
			}

			MemberViewInfo viewInfo = new MemberViewInfo(
				UiString.FromString(heading),
				UiString.FromString(title),
				UiString.FromString(longHeading),
				iconKey
				);

			return viewInfo;
		}
		public static string GetTypeReferenceText(Type type, MemberInfo memberContext)
		{
			return type.LocalName();// CSharpDocumentBuilder.GetMemberReferenceText(type, memberContext);
		}
		public static string BuildMethodBaseHeading(MethodBase method)
		{
			string name =
				method.Name
				+ "("
				+ string.Join(
					", ",
					method.GetParameters().Select(p => GetParameterText(method, p))
					)
				+ ")"
				;

			return name;
		}

		#endregion
	}
}
